package com.michaelbulava.TFS.ui.controls.browser;

//import com.sun.webpane.webkit.dom.JSObject;

import com.intellij.openapi.diagnostic.Logger;
import com.michaelbulava.TFS.log.FileLogger;
import javafx.application.Platform;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.concurrent.Worker;
import javafx.embed.swing.JFXPanel;
import javafx.event.EventHandler;
import javafx.geometry.Rectangle2D;
import javafx.scene.Scene;
import javafx.scene.web.PromptData;
import javafx.scene.web.WebEngine;
import javafx.scene.web.WebEvent;
import javafx.scene.web.WebView;
import javafx.util.Callback;
import netscape.javascript.JSObject;
import org.w3c.dom.Document;

import javax.swing.*;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import java.awt.*;
import java.io.ByteArrayOutputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

//import java.awt.*;

/**
 * Created by mbulava on 6/29/2015.
 */
public class FullFeaturedBrowser extends JPanel {
    private static final Logger log = FileLogger.getInstance(FullFeaturedBrowser.class);

    static final String APP_ID = "...";
    static final String REDIRECT_URL = "https://login.live.com/oauth20_desktop.srf";
    static final String RESPONSE_TYPE = "token";
    static final String SCOPE = "wl.signin%20wl.offline_access";

    private JFXPanel jfxPanel;
    private WebEngine engine;

    //private boolean mPageLoaded = false;
    //private final JPanel panel = new JPanel(new BorderLayout());
    private final JLabel lblStatus = new JLabel();
    private WebView view;
    private boolean debugOuput = false;
    private List<LocationListener> locationListenerList = new ArrayList<LocationListener>();
    private OnTitleChangeListener titleChangeListener;
    //private final JButton btnGo = new JButton("Go");
    //private final JTextField txtURL = new JTextField();
    //private final JProgressBar progressBar = new JProgressBar();

    public static class LocationEvent{
        public String location;
        public boolean doit = true;
    }

    public  interface LocationListener{

        void changing(LocationEvent event);

        void changed(LocationEvent event);
    }

    public interface OnTitleChangeListener{
        void titleChanged(String title);
    }

    private boolean ProcessLocationChanging(String location){
        LocationEvent event = new LocationEvent();
        event.location = location;
        for(LocationListener listener : locationListenerList){
            listener.changing(event);
            if(!event.doit){
                return false;
            }
        }
        return true;
    }

    public Object evaluate(String script) throws Exception{
        if (!script.startsWith("function") && script.contains("return")){
            script = "function wrapper(){" + script + "}\nwrapper();";
        }
        return engine.executeScript(script);
    }

    private void ProcessLocationChanged(String location){
        LocationEvent event = new LocationEvent();
        event.location = location;
        for (LocationListener listener : locationListenerList){
            listener.changed(event);
        }
    }

    public FullFeaturedBrowser(){
        super(new BorderLayout());

        Platform.setImplicitExit(false);

        jfxPanel = new JFXPanel();
        jfxPanel.setSize(250, 250);
        initComponents();

    }

    public void addLocationListener(LocationListener listener){
        locationListenerList.add(listener);
    }

    public void setOnTitleChangeListener(OnTitleChangeListener value){
        titleChangeListener = value;
    }

    public void removeLocationListener(LocationListener listener){
        locationListenerList.remove(listener);
    }

    private void initComponents() {
        createScene();

//        ActionListener al = new ActionListener() {
//            @Override
//            public void actionPerformed(ActionEvent e) {
//                loadURL(txtURL.getText());
//            }
//        };
//
//        btnGo.addActionListener(al);
//        txtURL.addActionListener(al);
//
//        progressBar.setPreferredSize(new Dimension(150, 18));
//        progressBar.setStringPainted(true);
//
//        JPanel topBar = new JPanel(new BorderLayout(5, 0));
//        topBar.setBorder(BorderFactory.createEmptyBorder(3, 5, 3, 5));
//        topBar.add(txtURL, BorderLayout.CENTER);
//        topBar.add(btnGo, BorderLayout.EAST);
//
//        JPanel statusBar = new JPanel(new BorderLayout(5, 0));
//        statusBar.setBorder(BorderFactory.createEmptyBorder(3, 5, 3, 5));
//        statusBar.add(lblStatus, BorderLayout.CENTER);
//        statusBar.add(progressBar, BorderLayout.EAST);

        //panel.add(topBar, BorderLayout.NORTH);
        add(jfxPanel, BorderLayout.CENTER);
        //panel.add(statusBar, BorderLayout.SOUTH);

        //getContentPane().add(panel);

        //setPreferredSize(new Dimension(1024, 600));
        //setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        //pack();
        setSize(250, 400);
    }

    private void createScene() {
        Platform.runLater(new Runnable() {
            @Override
            public void run() {

                view = new WebView();
                engine = view.getEngine();


                engine.titleProperty().addListener(new ChangeListener<String>() {
                    @Override
                    public void changed(ObservableValue<? extends String> observable, String oldValue, final String newValue) {
                        EventQueue.invokeLater(new Runnable() {
                            @Override
                            public void run() {
                                if (titleChangeListener != null)
                                    titleChangeListener.titleChanged(newValue);
                            }
                        });
                        SwingUtilities.invokeLater(new Runnable() {
                            @Override
                            public void run() {
                                //FullFeaturedBrowser.this.setTitle(newValue);
                            }
                        });
                    }
                });

                engine.setOnStatusChanged(new EventHandler<WebEvent<String>>() {
                    @Override
                    public void handle(final WebEvent<String> event) {
                        SwingUtilities.invokeLater(new Runnable() {
                            @Override
                            public void run() {
                                log.warn("onStatusChanged: " + event.getData());
                            }
                        });
                    }
                });



                engine.locationProperty().addListener(new ChangeListener<String>() {
                    @Override
                    public void changed(ObservableValue<? extends String> ov, String oldValue, final String newValue) {
                        Platform.runLater(new Runnable() {
                            @Override
                            public void run() {
                                log.warn("onLocationChange: " + newValue);
                                if (!ProcessLocationChanging(newValue)){
                                    engine.getLoadWorker().cancel();
                                    //log.warn("Not sure what to do when doit is false!!!");
                                    //view.setVisible(false);
                                }

                            }
                        });

                    }
                });

                engine.getLoadWorker().workDoneProperty().addListener(new ChangeListener<Number>() {
                    @Override
                    public void changed(final ObservableValue<? extends Number> observableValue, Number oldValue, final Number newValue) {
                        SwingUtilities.invokeLater(new Runnable() {
                            @Override
                            public void run() {
                                //progressBar.setValue(newValue.intValue());
                                //log.warn("workDone: " + newValue.toString());
                                EventQueue.invokeLater(new Runnable() {
                                    @Override
                                    public void run() {
                                        jfxPanel.setSize((int)view.getWidth(), (int)view.getHeight());
                                        log.warn("width: " + String.valueOf(view.getWidth()) + " height: " +
                                            String.valueOf(view.getWidth()));
                                        //FullFeaturedBrowser.this.setSize((int) view.getWidth(), (int) view.getHeight());
                                    }
                                });
                            }
                        });
                    }
                });

                engine.getLoadWorker()
                        .exceptionProperty()
                        .addListener(new ChangeListener<Throwable>() {

                            public void changed(ObservableValue<? extends Throwable> o, Throwable old, final Throwable value) {
                                if (engine.getLoadWorker().getState() == Worker.State.FAILED) {
                                    SwingUtilities.invokeLater(new Runnable() {
                                        @Override
                                        public void run() {
                                            JOptionPane.showMessageDialog(
                                                    FullFeaturedBrowser.this,
                                                    (value != null) ?
                                                            engine.getLocation() + "\n" + value.getMessage() :
                                                            engine.getLocation() + "\nUnexpected error.",
                                                    "Loading error...",
                                                    JOptionPane.ERROR_MESSAGE);
                                        }
                                    });
                                }
                            }
                        });

                engine.getLoadWorker().stateProperty().addListener(new ChangeListener<Worker.State>() {
                    @Override
                    public void changed(ObservableValue<? extends Worker.State> observableValue, Worker.State oldState,
                                        Worker.State newState) {
                        log.warn("stateProperty - " + newState);
                        if (newState == Worker.State.SUCCEEDED) {

                            if (debugOuput){
                                Document doc = engine.getDocument();
                                ByteArrayOutputStream out = new ByteArrayOutputStream();
                                DOMSource myDoc = new DOMSource(doc);
                                StreamResult result = new StreamResult(out);
                                try {
                                    Transformer transformer = TransformerFactory.newInstance().newTransformer();
                                    transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "no");
                                    transformer.setOutputProperty(OutputKeys.METHOD, "xml");
                                    transformer.setOutputProperty(OutputKeys.INDENT, "yes");
                                    transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
                                    transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "4");

                                    transformer.transform(myDoc,
                                            new StreamResult(out));
                                    log.warn(new String(out.toByteArray()));
                                } catch (Exception ex) {
                                    ex.printStackTrace();
                                }
                            }

                            ProcessLocationChanged(engine.getLocation());

                        }
                    }
                });

                //My Tests
                engine.setOnResized(new EventHandler<WebEvent<Rectangle2D>>() {
                    @Override
                    public void handle(WebEvent<Rectangle2D> event) {
                        FullFeaturedBrowser.this.setSize((int) event.getData().getWidth(),
                                (int) event.getData().getHeight());
                    }
                });

                engine.setPromptHandler(new Callback<PromptData, String>() {
                    @Override
                    public String call(PromptData promptData) {
                        log.warn(promptData.getMessage());
                        return null;
                    }
                });

                engine.setOnAlert(new EventHandler<WebEvent<String>>() {
                    @Override
                    public void handle(WebEvent<String> stringWebEvent) {
                        log.warn("onAlert: " + stringWebEvent.getData());
                    }
                });

                engine.setConfirmHandler(new Callback<String, Boolean>() {
                    @Override
                    public Boolean call(String s) {
                        log.warn("onConfirm: " + s);
                        return null;
                    }
                });

                engine.getLoadWorker().runningProperty().addListener(new ChangeListener<Boolean>() {
                    @Override
                    public void changed(ObservableValue<? extends Boolean> observableValue,
                                        Boolean oldValue, Boolean newValue) {
                        log.warn("running: " + newValue.toString());
                    }
                });

                engine.getLoadWorker().valueProperty().addListener(new ChangeListener<Void>() {
                    @Override
                    public void changed(ObservableValue<? extends Void> observableValue, Void aVoid, Void t1) {
                        log.warn("OnValue Property;");
                    }
                });

                engine.setOnStatusChanged(new EventHandler<WebEvent<String>>() {
                    public void handle(WebEvent<String> event) {
                        if (event.getSource() instanceof WebEngine) {
                            WebEngine we = (WebEngine) event.getSource();
                            String location = we.getLocation();
                            if (location.startsWith(REDIRECT_URL) && location.contains("access_token")) {
                                try {
                                    URL url = new URL(location);
                                    String[] params = url.getRef().split("&");
                                    Map<String, String> map = new HashMap<String, String>();
                                    for (String param : params) {
                                        String name = param.split("=")[0];
                                        String value = param.split("=")[1];
                                        map.put(name, value);
                                    }
                                    System.out.println("The access token: " + map.get("access_token"));
                                    //stage.hide();
                                } catch (MalformedURLException e) {
                                    e.printStackTrace();
                                }
                            }
                        }
                    }
                });

                //engine.setJavaScriptEnabled(true);
//                if (!engine.isJavaScriptEnabled())
//                    log.warn("No JavaScript :(");

                jfxPanel.setScene(new Scene(view));
            }
        });
//        engine.locationProperty().addListener(new ChangeListener<String>() {
//            @Override
//            public void changed(ObservableValue<? extends String> ov, String oldValue, final String newValue) {
//                SwingUtilities.invokeLater(new Runnable() {
//                    @Override
//                    public void run() {
//                        //txtURL.setText(newValue);
//                    }
//                });
//            }
//        });

//        engine.getLoadWorker().workDoneProperty().addListener(new ChangeListener<Number>() {
//            @Override
//            public void changed(ObservableValue<? extends Number> observableValue, Number oldValue, final Number newValue) {
//                SwingUtilities.invokeLater(new Runnable() {
//                    @Override
//                    public void run() {
//                        //progressBar.setValue(newValue.intValue());
//                    }
//                });
//            }
//        });

//        engine.getLoadWorker()
//                .exceptionProperty()
//                .addListener(new ChangeListener<Throwable>() {
//
//                    public void changed(ObservableValue<? extends Throwable> o, Throwable old, final Throwable value) {
//                        if (engine.getLoadWorker().getState() == FAILED) {
//                            SwingUtilities.invokeLater(new Runnable() {
//                                @Override
//                                public void run() {
//                                    JOptionPane.showMessageDialog(
//                                            FullFeaturedBrowser.this,
//                                            (value != null) ?
//                                                    engine.getLocation() + "\n" + value.getMessage() :
//                                                    engine.getLocation() + "\nUnexpected error.",
//                                            "Loading error...",
//                                            JOptionPane.ERROR_MESSAGE);
//                                }
//                            });
//                        }
//                        mPageLoaded = true;
//
//                    }
//                });

//        engine.getLoadWorker().stateProperty().addListener(new ChangeListener<Worker.State>() {
//            @Override
//            public void changed(ObservableValue<? extends Worker.State> observableValue, Worker.State oldState,
//                                Worker.State newState) {
//                if (newState == Worker.State.SUCCEEDED){
//                    JSObject win = (JSObject)engine.executeScript("window");
//                    win.setMember("notifyToken", new MyTokenHandler());
//                }
//
//            }
//        });
//
//
//        engine.setPromptHandler(new Callback<PromptData, String>() {
//            @Override
//            public String call(PromptData promptData) {
//                log.warn("Message: " + promptData.getMessage());
//                log.warn("Default Value: " + promptData.getDefaultValue());
//                return null;
//            }
//        });
    }

    public void setJavaEnabled(final boolean value){
        Platform.runLater(new Runnable() {
            @Override
            public void run() {
                engine.setJavaScriptEnabled(value);
            }
        });
    }

    public void navigate(final String navigateToUrl){
//        Platform.runLater(new Runnable() {
//            @Override
//            public void run() {
//                String tmp = toURL(navigateToUrl);
//
//                if (tmp == null) {
//                    tmp = toURL("http://" + url);
//                }
//
//                engine.load(tmp);
//            }
//        });
        if (engine == null){
            Platform.runLater(new Runnable() {
                @Override
                public void run() {
                    engine.load(navigateToUrl);
                }
            });
        } else {
            engine.load(navigateToUrl);
        }

    }

    private static String toURL(String str) {
        try {
            return new URL(str).toExternalForm();
        } catch (MalformedURLException exception) {
            return null;
        }
    }



    public void registerFunction(final String functionName, final Object function){

        Platform.runLater(new Runnable() {
            @Override
            public void run() {
                engine.getLoadWorker().stateProperty().addListener(new ChangeListener<Worker.State>() {
                    @Override
                    public void changed(ObservableValue<? extends Worker.State> observableValue,
                                        Worker.State oldState, Worker.State newState) {
                        if (newState == Worker.State.SUCCEEDED) {

                            if (engine.getLoadWorker().getException() != null)
                                log.error("hmm?", engine.getLoadWorker().getException());

                            JSObject win = (JSObject) engine.executeScript("window");
                            win.setMember(functionName, function);
                            win.setMember("notifyError", function);

                        }
                    }
                });

                //com.sun.webpane.webkit.dom.JSObject

            }
        });
    }

    private class MyTokenHandler {
        public void notifyToken(Object[] obj){
            log.warn("lookie here: " + obj);
        }
    }
}
